﻿using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Windows.Forms.VisualStyles;

using gKodes.IO;
using gKodes.Drawing;
using gKodes.Collections;
using gKodes.Windows.Forms.Internals;
using System.Text.RegularExpressions;

namespace gKodes.Windows.Forms
{
    public enum OffsetState : int
    {
        Default = 0,
        Highlight = 1,
        Changed = 2,
        Disabled = 4
    }

    public class HexView : ControlPage
    {
        /// <summary>
        /// Used for storing VisualStyleElement which is used in rendering the VisualStyle
        /// </summary>
        private static readonly VisualStyleElement VSElement = VisualStyleElement.TextBox.TextEdit.Normal;

        private TextBoxState mtbState;

        private VisualStyleRenderer vsRenderer;

        private CaretManger mCManager;

        private Internals.HexViewSegment baseSegment;

        /// <summary>
        /// Used for storing the font size needs to be applied while drawing on the Page
        /// </summary>
        private Size mfSize;

        private IDataSource mSource;

        private Controller mController;

        protected Size FontSize { get { return this.mfSize; } }

        public IDataSource Source
        {
            get { return this.mSource; }
            set
            {
                this.mSource = value;
                this.OnDataSourceChanged(EventArgs.Empty);
                this.Invalidate();
            }
        }

        public Controller IController
        {
            get { return this.mController; }
            set
            {
                if (this.mController != null)
                    this.mController.UnRegister();
                this.mController = value;
                value.Register(this, this.mCManager);
            }
        }

        public DataSizes DataSize { get; set; }

        public Encoding Encoder { get; set; }

        public TextBoxState State
        {
            get { return this.mtbState; }
        }

        public bool CanUndo { get { return false; } }

        public bool ReadOnly
        {
            get
            {
                return (this.mtbState & TextBoxState.Readonly) == TextBoxState.Readonly;
            }
        }

        public int Rows
        {
            get { return this.baseSegment.Rows; }
        }

        public int Columns
        {
            get { return this.baseSegment.Columns; }
        }

        public long TableSize
        {
            get { return this.baseSegment.Size; }
        }

        public event EventHandler DataSourceChanged;

        public HexView()
            : base()
        {
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.PaintSchemes.Change(1, (SolidPaint)Color.White, (SolidPaint)Color.Blue);
            this.PaintSchemes.ChangeForePaint(2, (SolidPaint)Color.Red);
            this.PaintSchemes.Change(4,
                (SolidPaint)SystemColors.ControlLight, (SolidPaint)SystemColors.Control);
            
            this.Encoder = (Encoding)Encoding.ASCII.Clone();
            this.Encoder.DecoderFallback = new DecoderReplacementFallback(".");

            this.mCManager = new CaretManger(this);
            this.SetState(TextBoxState.Normal);

            Internals.OffsetSegment opSegment = new Internals.OffsetSegment();
            this.Segments.Add(opSegment);

            this.baseSegment = new Internals.HexViewSegment();
            baseSegment.Padding = new Padding(2, 0, 2, 0);
            this.Segments.Add(baseSegment);
            
            Internals.TextSegment tSegment = new Internals.TextSegment();
            this.Segments.Add(tSegment);

            opSegment.CellProperties = new CellProperties(new Size(8, 1), Padding.Empty);
            tSegment.CellProperties = new CellProperties(new Size(1, 1), Padding.Empty);
            baseSegment.CellProperties = new CellProperties(new Size(2, 1), new Padding(2, 0, 2, 0));

            this.IController = new Controller();
            this.OnFontChanged(EventArgs.Empty);
        }
        
        public void SetState(TextBoxState tbState)
        {
            if (this.mtbState != tbState)
            {
                this.mtbState = tbState;
                switch (this.mtbState)
                {
                    case TextBoxState.Selected:
                        if (!this.Focused)
                            this.Focus();
                        break;
                    case TextBoxState.Readonly:
                        break;
                    case TextBoxState.Disabled:
                        this.Enabled = false;
                        break;
                }

                if (VisualStyleRenderer.IsSupported)
                    this.vsRenderer = new VisualStyleRenderer(VSElement.ClassName, VSElement.Part, (int)mtbState);
                this.UpdateBuffer();
            }
        }

        public override void InvalidatePage()
        {
            if (VisualStyleRenderer.IsSupported)
                base.InvalidatePage(this.vsRenderer.GetBackgroundContentRectangle(
                    this.CreateGraphics(), this.ClientRectangle));
            else
                base.InvalidatePage(
                    new Rectangle(2, 2, this.ClientRectangle.Width - 4, this.ClientRectangle.Height - 4));
            this.UpdateBuffer();
        }

        protected override void OnFontChanged(EventArgs e)
        {
            //TODO: split this into two methods
            SizeF fSize = this.CreateGraphics().MeasureString(
                this.Encoder.GetString(new byte[] { 65 }), 
                this.Font, 100, this.StringFormat);

            this.mfSize = Size.Ceiling(fSize);

            for (int i = 0; i < this.Segments.Count; i++)
            {
                if (this.Segments[i] is HexView.PageSegment)
                    (this.Segments[i] as HexView.PageSegment).Validate();
            }

            this.InvalidatePage();

            base.OnFontChanged(e);
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            this.SetState(TextBoxState.Selected);
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            this.SetState(TextBoxState.Normal);
        }

        //private AssociatedRange asr = new AssociatedRange();

        protected override void OnPaint(PaintEventArgs e)
        {
            if (VisualStyleRenderer.IsSupported)
                this.vsRenderer.DrawBackground(e.Graphics, e.ClipRectangle);
            else { this.Graphics.FillRectangle(new SolidBrush(this.BackColor), e.ClipRectangle); }

            if (this.Source != null)
            {
                foreach (PageSegment ps in this.Segments)
                {
                    ps.OffsetStates = this.IController.GetRanges();
                }
                base.OnPaint(e);
            }
            //TODO: render carets etc here
            this.mCManager.Render(e.Graphics, e.ClipRectangle);
        }

        protected void UpdateBuffer()
        {
            this.Invalidate();
        }

        #region Page Segmnet

        public new abstract class PageSegment : ControlPage.PageSegment
        {
            private Size mcSize;

            private int mtRows;

            private int mtColumns;

            private int mtSize;

            public virtual Size CellSize
            {
                get { return this.mcSize; }
            }

            public virtual CellProperties CellProperties { get; internal set; }

            //TODO the needed properties to it here
            public new HexView DrawnOn
            {
                get { return (HexView)base.DrawnOn; }
                internal set { base.DrawnOn = value; }
            }

            public virtual bool IsNavigable { get { return false; } }

            internal protected int NavIndex { get; internal set; }

            protected internal IRangeEnumerator OffsetStates { get; internal set; }

            public virtual int Rows { get { return this.mtRows; } }

            public virtual int Columns { get { return this.mtColumns; } }

            public virtual long Size { get { return this.mtSize; } }

            protected internal virtual void Validate()
            {
                this.mcSize = new Size(
                    (this.CellProperties.Size.Width * this.DrawnOn.FontSize.Width) + this.CellProperties.Padding.Horizontal
                , (this.CellProperties.Size.Height * this.DrawnOn.FontSize.Height) + this.CellProperties.Padding.Vertical);
            }

            public override void ResizeTo(Size size)
            {
                //Size tSize = this.GetCellSize();

                if (this.CellSize.Width > 0 && this.CellSize.Height > 0)
                {
                    this.mtColumns = (size.Width - this.Padding.Horizontal) / mcSize.Width;
                    this.mtRows = (int)Math.Ceiling(
                        (double)(size.Height - this.Padding.Vertical) / mcSize.Height);

                    this.mtSize = this.mtColumns * mtRows;

                    //tSize = new Size(size.Width - this.Padding.Horizontal,
                    //    size.Height - this.Padding.Vertical);

                    this.ClientRectangle = new Rectangle(this.Padding.Left, this.Padding.Top,
                        mtColumns * this.CellSize.Width, mtRows * this.CellSize.Height);

                    this.DisplayRectangle = new Rectangle(0,0, 
                        this.ClientRectangle.Width + this.Padding.Horizontal, size.Height);
                }
            }

            public override void RenderSegment(Rectangle clip)
            {
                throw new NotImplementedException();
            }

            public virtual string DrawingHelper()
            {
                throw new NotImplementedException();
            }

            #region Table method's

            public bool Contains(float x, float y)
            {
                return (x > -1 && x < this.Columns && y > -1 && y < this.Rows);
            }

            public bool Contains(double index)
            {
                return (index > -1 && index < this.Size);
            }

            public PointF CoordinatesOf(double index)
            {
                if (this.CellProperties.Dividend == 1)
                    index = Math.Round(index);

                return new PointF((float)(index % this.Columns), ((int)index / this.Columns));
            }

            public Point CoordinatesOf(int x, int y)
            {
                if (this.DisplayRectangle.Contains(x, y))
                {
                    return new Point(
                        (x - this.Padding.Left) / this.CellSize.Width,
                        (y - this.Padding.Top) / this.CellSize.Height);
                }
                throw new ArgumentOutOfRangeException("The given offsets x, y are out of range");
            }

            public double IndexOf(float x, float y)
            {
                return ((int)y * this.Columns) + x;
            }

            public Point LocationOf(float x, float y)
            {
                if (this.Contains(x, y))
                {
                    if (this.CellProperties.Dividend == 1) { x = (int)x; }
                    return new Point(
                        (int)(x * this.CellSize.Width) + this.Padding.Left,
                        ((int)y * this.CellSize.Height) + this.Padding.Top);
                }
                throw new ArgumentOutOfRangeException("The given offsets x, y are out of range");
            }

            public Point LocationOf(long index)
            {
                PointF pt = this.CoordinatesOf(index);
                return this.LocationOf(pt.X, pt.Y);
            }

            #endregion

            protected Region RegionOf(float x, float y)
            {
                Point pt = this.LocationOf((int)x, (int)y);
                return new Region(
                        new Rectangle(pt, this.CellSize));
            }

            protected Region RegionOf(Range range)
            {
                return this.RegionOf(this.CoordinatesOf(range.Start), 
                    this.CoordinatesOf(range.End));
            }

            protected Region RegionOf(PointF pt1, PointF pt2)
            {
                if (pt1 == pt2)
                    return this.RegionOf(pt1.X, pt1.Y);
                else
                {
                    Point loc1 = this.LocationOf(pt1.X, pt1.Y);
                    Point loc2 = this.LocationOf(pt2.X, pt2.Y);
                    if (pt1.Y == pt2.Y)
                    {
                        return new Region(Rectangle.FromLTRB(loc1.X, loc1.Y,
                            loc2.X + this.CellSize.Width, loc2.Y + this.CellSize.Height));
                    }
                    //Info: if points are fare appart
                    Region pregion = new Region(Rectangle.FromLTRB(loc1.X, loc1.Y,
                            this.ClientRectangle.Right, loc1.Y + this.CellSize.Height));
                    pregion.Union(Rectangle.FromLTRB(this.ClientRectangle.X, 
                        loc1.Y + this.CellSize.Height, this.ClientRectangle.Right, loc2.Y));
                    pregion.Union(Rectangle.FromLTRB(this.ClientRectangle.X,
                        loc2.Y, loc2.X + this.CellSize.Width, loc2.Y + this.CellSize.Height));
                    return pregion;
                }
            }

            #region Drawing Help method's

            protected void DrawStringInCell(long index, string s, IPaint p)
            {
                PointF pt = this.CoordinatesOf(index);
                this.DrawStringInCell((int)pt.X, (int)pt.Y, s, p);
            }

            protected void DrawStringInCell(int x, int y, string s, IPaint p)
            {
                if (s.Length != this.CellProperties.Size.Width)
                    throw new Exception("String is over the cell size");

                Point point = this.LocationOf(x, y);
                this.DrawString(this.CellProperties.Padding.Left + point.X,
                        this.CellProperties.Padding.Top + point.Y, s, p);
            }

            protected void FillCell(Range range, IPaint p)
            {
                using (Region r = this.RegionOf(range))
                {
                    this.Graphics.FillRegion(p.Brush, r);
                }
            }

            protected void FillCell(long start, long end, IPaint p) { }

            protected void FillCell(int x, int y, IPaint p)
            {
            }

            protected void DrawForeHelper(Range range, IPaint p)
            {
                this.DrawForeHelper(range.Start, range.End, p);
            }

            protected void DrawForeHelper(long start, long end, IPaint p)
            {
                for (long i = start; i <= end; i++)
                {
                    Point pt = Point.Round(this.CoordinatesOf(i));
                    this.DrawStringInCell(pt.X, pt.Y, this.HelperSource(pt.X, pt.Y, i), p);
                }
            }

            protected void DrawForeHelper(Point cr1, Point cr2, IPaint p)
            {
                for (int r = cr1.Y; r <= cr2.Y; r++)
                {
                    for (int c = cr1.X; c <= cr2.X; c++)
                    {
                        this.DrawStringInCell(c, r, this.HelperSource(c, r, (long)this.IndexOf(c, r)), p);
                    }
                }
            }

            protected virtual string HelperSource(int x, int y, long i)
            {
                throw new NotImplementedException();
            }

            #endregion

            #region Caret

            protected class Caret : CaretManger.ICaret
            {
                private PageSegment mBelongsTo;

                private Point mLocation;

                private PointF mCord;

                private double mIndex;

                private Size mSize;

                private bool mVertical;

                public Caret(PageSegment ps)
                {
                    this.Visible = true;
                    this.mBelongsTo = ps;
                    this.Vertical = false;
                }

                public PointF Coordinates
                {
                    get { return this.mCord; }
                }

                public Point Location
                {
                    get { return this.mLocation; }
                }

                public Size Size
                {
                    get { return this.mSize; }
                }

                public bool Visible { get; set; }

                public bool Frozen { get; set; }

                public bool SetPoint(float x, float y)
                {
                    if (this.BelongsTo.Contains(x, y))
                    {
                        this.mCord = new PointF((this.Vertical)? ((int)x) : x, y);
                        this.mIndex = this.BelongsTo.IndexOf(this.mCord.X, this.mCord.Y);

                        this.mLocation = this.mBelongsTo.LocationOf(this.mCord.X, this.mCord.Y);

                        if ((this.mCord.X % 1) == 0)
                            this.mLocation.X += this.BelongsTo.CellProperties.Padding.Left;

                        if (this.Vertical)
                            this.mLocation.Y += this.BelongsTo.CellSize.Height;
                        return true;
                    }
                    return false;
                }

                public PageSegment BelongsTo
                {
                    get { return this.mBelongsTo; }
                }

                public bool Vertical
                {
                    get { return this.mVertical; }
                    set
                    {
                        this.mVertical = value;
                        this.Validate();
                    }
                }

                public void Validate()
                {
                    if (this.Vertical)
                        this.mSize = new Size(this.BelongsTo.CellSize.Width
                            - this.BelongsTo.CellProperties.Padding.Horizontal, 2);
                    else { this.mSize = new Size(1, this.BelongsTo.CellSize.Height); }
                }
            }

            #endregion

            public CaretManger.ICaret GetNavigator()
            {
                if (this.IsNavigable)
                {
                    return new Caret(this);
                }
                throw new InvalidOperationException("This segement is not navigable");
            }
        }   

        #endregion

        protected override void OnSegmentAdded(SegmentEventArgs e)
        {
            base.OnSegmentAdded(e);
            //Info: register into caret manager
            if (e.Segment is PageSegment)
            {
                HexView.PageSegment segmnet = (HexView.PageSegment)e.Segment;
                if (segmnet.IsNavigable)
                {
                    segmnet.NavIndex = 
                        this.mCManager.CreateCaret(segmnet.GetNavigator());
                }
            }
        }

        protected override void OnSegmentRemoved(SegmentEventArgs e)
        {
            base.OnSegmentRemoved(e);
            //Info: unregister from caret manager
            if (e.Segment is PageSegment)
            {
                this.mCManager.DestroyCaret(
                    (e.Segment as HexView.PageSegment).NavIndex);
            }
        }

        protected virtual void OnDataSourceChanged(EventArgs e)
        {
            if (this.DataSourceChanged != null)
                this.DataSourceChanged(this, e);
        }

        #region Caret Manager

        public class CaretManger : IDisposable
        {
            #region ICaret

            public interface ICaret
            {
                PointF Coordinates { get; }

                /// <summary>
                /// Gets the location at which the caret has been drawn
                /// </summary>
                Point Location { get; }

                /// <summary>
                /// Gets the size of the Caret
                /// </summary>
                Size Size { get; }

                /// <summary>
                /// Gets a value indicating whether the caret is visible.
                /// </summary>
                bool Visible { get; set; }

                /// <summary>
                /// Gets a value indicating whether the caret is frozen or not.
                /// </summary>
                bool Frozen { get; set; }

                /// <summary>
                /// Lets the set the postion of the Caret
                /// </summary>
                /// <param name="x"></param>
                /// <param name="y"></param>
                /// <returns></returns>
                bool SetPoint(float x, float y);

                /// <summary>
                /// Gets the PageSegment to which this caret Belongs To.
                /// </summary>
                HexView.PageSegment BelongsTo { get; }

                /// <summary>
                /// Get or set a Boolean value indicating whether the caret is draw 
                /// vertically or not
                /// </summary>
                bool Vertical { get; set; }

                void Validate();
            }

            #endregion

            //TODO: need to have timer and an interface for getting caret information
            /// <summary>
            /// Timer used for making carets blink
            /// </summary>
            private Timer mbTimer;

            private Region mcRegion;

            private Control BelongsTo;

            private bool InFocus;

            protected bool Flick;

            protected bool mVisible;

            private List<ICaret> Carets;

            public PointF Location { get; private set; }

            public bool Visible
            {
                get { return this.mVisible; }
                set
                {
                    this.mVisible = value;
                    this.mbTimer.Enabled = value;
                }
            }

            public CaretManger(Control control)
            {
                this.BelongsTo = control;
                this.BelongsTo.FontChanged += new EventHandler(OnFontChanged);
                this.Carets = new List<ICaret>();
                this.mcRegion = new Region();
                this.InFocus = control.Focused;
                this.Flick = true;
                this.mbTimer = new Timer();
                this.mbTimer.Interval = SystemInformation.CaretBlinkTime;
                this.mbTimer.Tick += new EventHandler(OnTick);
            }

            public ICaret this[int index] { get { return this.Carets[index]; } }

            internal protected virtual void Render(Graphics g, Rectangle r)
            {
                //System.Diagnostics.Debug.WriteLine(this.Visible + "" + this.Flick + "" + r);
                if (this.Visible)
                {
                    foreach (ICaret caret in this.Carets)
                    {
                        if (caret.Visible && (this.Flick || caret.Vertical))
                        {
                            g.FillRectangle(Brushes.Black, caret.BelongsTo.Location.X + caret.Location.X,
                                    caret.BelongsTo.Location.Y + caret.Location.Y, caret.Size.Width, caret.Size.Height);
                        }
                    }
                }
            }

            internal protected virtual int CreateCaret(ICaret caret)
            {
                caret.Visible = true;
                this.Carets.Add(caret);
                this.mcRegion.Union(new Rectangle(caret.Location, caret.Size));
                return this.Carets.Count - 1;
            }

            internal protected virtual void DestroyCaret(int index)
            {
                this.Carets.RemoveAt(index);
                //this.mcRegion.Exclude(new Rectangle(caret.Location, caret.Size));
            }

            internal protected virtual void DestroyCaret(ICaret caret)
            {
                caret.Visible = false;
                if (this.Carets.Remove(caret))
                    this.mcRegion.Exclude(new Rectangle(caret.Location, caret.Size));
            }

            public bool SetCaretPos(PointF pt)
            {
                return this.SetCaretPos(pt.X, pt.Y);
            }

            public bool SetCaretPos(float x, float y)
            {
                this.BelongsTo.Invalidate(this.mcRegion);

                this.mcRegion.MakeEmpty();
                foreach (ICaret caret in this.Carets)
                {
                    caret.SetPoint(x, y);
                    this.mcRegion.Union(
                        new Rectangle(caret.BelongsTo.Location + (Size)caret.Location, caret.Size));
                }
                this.Location = new PointF(x, y);
                this.BelongsTo.Invalidate(this.mcRegion);

                return true;
            }

            public void Freeze()
            {
                if (this.mbTimer.Enabled)
                {
                    this.Flick = true;
                    this.mbTimer.Stop();
                }
            }

            public void Melt()
            {
                if (!this.mbTimer.Enabled)
                    this.mbTimer.Start();
            }

            protected virtual void OnTick(object sender, EventArgs e)
            {
                this.Flick = (!this.Flick);//| !this.mbTimer.Enabled
                this.BelongsTo.Invalidate(this.mcRegion);
            }

            private void OnFontChanged(object sender, EventArgs e)
            {
                foreach (ICaret caret in this.Carets)
                    caret.Validate();
            }

            public void Dispose()
            {
                this.Dispose(true);
            }

            private void Dispose(bool native)
            {
                this.mbTimer.Dispose();
                this.mcRegion.Dispose();
            }
        }

        #endregion

        private void ShowVScrollBar(long max)
        {
            this.VScroll.Maximum = (int)max;
            if (!this.VScroll.Visible)
            {
                this.VScroll.Visible = true;
                this.InvalidatePage();
            }
        }

        private void ShowHScrollBar(long max)
        {
            this.HScroll.Maximum = (int)max;
            if (!this.HScroll.Visible)
            {
                this.HScroll.Visible = true;
                this.InvalidatePage();
            }
        }

        private void SetVScrollValue(int value) { this.VScroll.Value = value; }

        private void SetHScrollValue(int value) { this.HScroll.Value = value; }

        private void HideVScrollBar()
        {
            this.VScroll.Visible = false;
            this.InvalidatePage();
        }

        private void HideHScrollBar()
        {
            this.HScroll.Visible = false;
            this.InvalidatePage();
        }

        #region Controller

        public class Controller
        {
            /// <summary>
            /// Used for storing the Range in which the caret can move around
            /// </summary>
            private Range mCRange;

            /// <summary>
            /// Used for storing the range of visible bytes those have been drawn on the screen
            /// </summary>
            private Range mVRange;

            private Range mARange;

            private Range mSelection;

            private double mCOffset; // Current Caret Offset

            private PointF mCPoint;

            /// <summary>
            /// This is used for storing the current line at which the caret is located
            /// </summary>
            private long mCLine;

            /// <summary>
            /// This is used for storing the last line number upto which the caret
            /// can move
            /// </summary>
            private long mlVLine;

            /// <summary>
            /// This is used for stroing the last Visible line n
            /// </summary>
            private int mXHeight;

            private AssociatedRange mOffsetsRange;
            
            private bool InSelection; // Is used to indicate if selection is in action or not

            public double CaretOffset { get { return this.mCOffset; } }

            public PointF Caret { get { return this.mCPoint; } }

            protected HexView BelongsTo { get; private set; }

            protected CaretManger CaretManger { get; private set; }

            protected HexView.PageSegment InFocus { get; private set; }

            public Range VisibleRange { get { return this.mVRange; } }

            public virtual Range AccessibleRange { get { return this.mARange; } }

            public virtual Range CaretRange
            {
                get { return this.mCRange; }
            }

            public event CaretOffsetEvent CaretOffsetChanged;

            #region Register & Un-register

            protected internal virtual void Register(HexView hv, CaretManger cm)
            {
                this.BelongsTo = hv;
                this.CaretManger = cm;
                this.mOffsetsRange = new AssociatedRange();

                this.InFocus = (HexView.PageSegment) hv.Segments["Text"];
                this.OnSegmentFocusChanged(hv, new SegmentEventArgs(hv.Segments["Hex"]));
                
                this.BelongsTo.DataSourceChanged += new EventHandler(OnDataSourceChanged);
                this.BelongsTo.PageInvalidated += new EventHandler(OnPageInvalidated);
                this.BelongsTo.GotFocus += new EventHandler(FocusChanged);
                this.BelongsTo.LostFocus += new EventHandler(FocusChanged);
                this.BelongsTo.Scroll += new ScrollEventHandler(OnScroll);
                this.BelongsTo.MouseDown += new MouseEventHandler(OnMouseDown);
                this.BelongsTo.MouseWheel += new MouseEventHandler(OnMouseWheel);
                this.BelongsTo.PreviewKeyDown += new PreviewKeyDownEventHandler(OnPreviewKeyDown);
                this.BelongsTo.SegmentFocusChanged += new SegmentEvent(OnSegmentFocusChanged);
                this.BelongsTo.KeyUp += new KeyEventHandler(OnKeyUp);
                this.BelongsTo.KeyPress += new KeyPressEventHandler(OnKeyPress);
            }

            protected internal virtual void UnRegister()
            {
                this.BelongsTo.DataSourceChanged -= new EventHandler(OnDataSourceChanged);
                this.BelongsTo.PageInvalidated -= new EventHandler(OnPageInvalidated);
                this.BelongsTo.GotFocus -= new EventHandler(FocusChanged);
                this.BelongsTo.LostFocus -= new EventHandler(FocusChanged);
                this.BelongsTo.Scroll -= new ScrollEventHandler(OnScroll);
                this.BelongsTo.MouseDown -= new MouseEventHandler(OnMouseDown);
                this.BelongsTo.MouseWheel -= new MouseEventHandler(OnMouseWheel);
                this.BelongsTo.PreviewKeyDown -= new PreviewKeyDownEventHandler(OnPreviewKeyDown);
                this.BelongsTo.SegmentFocusChanged -= new SegmentEvent(OnSegmentFocusChanged);
                this.BelongsTo.KeyUp -= new KeyEventHandler(OnKeyUp);
                this.BelongsTo.KeyPress -= new KeyPressEventHandler(OnKeyPress);
            }

            #endregion

            //AssociatedRange.Enumerator
            protected internal virtual IRangeEnumerator GetRanges()
            {
                return (IRangeEnumerator)this.mOffsetsRange.Intersect(this.mARange);
            }

            #region Navigation method's

            protected void UpdateBuffer()
            {
                this.BelongsTo.UpdateBuffer();
            }

            protected void UpdateLineCount()
            {
                if (this.BelongsTo.Source != null)
                {
                    this.mlVLine = (long)Math.Ceiling(
                        (decimal)this.BelongsTo.Source.Length / this.BelongsTo.Columns) - this.mXHeight;
                    if (this.BelongsTo.IsOverflow)
                        this.mlVLine++;
                    if (this.mlVLine > this.mXHeight)
                        this.BelongsTo.ShowVScrollBar(this.mlVLine);
                }
                else { this.mlVLine = 0; }
            }

            private void ValidateSelection()
            {
                //if ((this.mSelection.End + 1) != this.mCOffset)
                //    this.mSelection = Range.Empty;
                if (this.mOffsetsRange.Count > 0)
                    this.mOffsetsRange.Remove(1);
                if (this.InSelection)
                {
                    //if ((this.mSelection.End + 1) != this.mCOffset)
                    if (this.mSelection.Start == 0 && this.mSelection.End == 0)
                        this.mSelection.Start = (long)this.mCOffset;
                    this.mSelection.End = (long)this.mCOffset;
                    System.Diagnostics.Debug.WriteLine(this.mSelection);
                    this.mOffsetsRange.Add(this.mSelection, 1);
                }
                else
                    this.mSelection = Range.Empty;
            }

            protected bool UpdateInputBuffer(long foffset)
            {
                if (this.BelongsTo.Source != null)
                {
                    Range trange = this.BelongsTo.Source.Load(foffset,
                        (int)this.BelongsTo.TableSize);
                    if (trange.Length > 0)
                    {
                        this.mARange = trange;
                        this.mVRange = trange;
                        if (this.BelongsTo.IsOverflow)
                            this.mVRange.Length -= this.BelongsTo.Columns;
                        return true;
                    }
                }
                return false;
            }

            protected void MoveToOffset(double offset)
            {
                //ushort
                //Scroll to the offset
                offset = Math.Max(this.mCRange.Start, Math.Min(offset, this.mCRange.End));
                if (!this.VisibleRange.Contains((long)offset))
                {
                    long value = (long)(offset / this.BelongsTo.Columns);
                    if ((this.mCLine + this.mXHeight) == value)//Info: for smoot scroling, move to next line on page
                        value -= (this.mXHeight - 1);
                    
                    if (!this.MoveToLine(value))
                        return;
                }
                this.mCOffset = offset;
                this.ValidateCaret();
                if(CaretOffsetChanged != null)
                    this.CaretOffsetChanged(this, new CaretOffsetEventArgs(offset));
                this.ValidateSelection();
                this.UpdateBuffer();
            }

            protected virtual bool SetOffset(double index)
            {
                if (index > -1 && index < this.BelongsTo.TableSize)
                {
                    //this.CaretManger.SetCaretPos(this.InFocus.CoordinatesOf(index));
                    this.ValidateCaret();
                    return true;
                }
                return false;
            }

            protected void SetCaretRow(int row)
            {
                this.SetCaret(this.mCPoint.X, row);
            }

            protected void SetCaretColumn(float column)
            {
                this.SetCaret(column, this.mCPoint.Y);
            }

            protected void SetCaret(Point pt)
            {
                this.SetCaret(pt.X, pt.Y);
            }

            protected void SetCaret(float column, float row)
            {
                if (this.InFocus.Contains(column, row))
                {
                    this.CaretManger.Freeze();
                    this.MoveToOffset(this.mARange.Start + this.InFocus.IndexOf(column, row));
                    this.CaretManger.Melt();
                }
            }

            protected bool MoveByLine(int value)
            {
                return this.MoveToLine(this.mCLine + value);
            }

            protected virtual bool MoveToLine(long line)
            {
                if (this.BelongsTo.Source != null)
                {
                    line = Math.Max(0, Math.Min(line, this.mlVLine));

                    if (this.UpdateInputBuffer(line * this.BelongsTo.Columns))
                    {
                        this.mCLine = line;
                        this.BelongsTo.SetVScrollValue((int)line);
                        this.ValidateCaret();
                        return true;
                    }
                }
                return false;
            }

            public void ValidateCaret() //TODO: Use this function for on focus also
            {
                if (this.BelongsTo.Source != null && !this.InSelection)
                {
                    if ((this.CaretManger.Visible = (this.BelongsTo.Focused
                            && this.AccessibleRange.Contains((long)this.CaretOffset))))
                    {
                        this.CaretManger.SetCaretPos(this.InFocus.CoordinatesOf(
                            (float)(this.mCOffset - this.AccessibleRange.Start)));
                    }
                }
                else { this.CaretManger.Visible = false; }
            }

            #endregion

            protected virtual void OnDataSourceChanged(object sender, EventArgs e)
            {
                this.mCPoint = PointF.Empty;

                this.mCRange = (this.BelongsTo.Source != null) ?
                    new Range(0, this.BelongsTo.Source.Length) : Range.Empty;

                this.UpdateLineCount();
                this.MoveToLine(0);
                this.MoveToOffset(0);
                this.UpdateBuffer();
            }

            protected virtual void OnPageInvalidated(object sender, EventArgs e)
            {
                if (this.BelongsTo.Columns > 0)
                {
                    this.mXHeight = this.BelongsTo.Rows;
                    
                    //TODO: add 1 if there is overflow
                    if (this.BelongsTo.IsOverflow)
                        this.mXHeight -= 1;
                    
                    this.UpdateLineCount();

                    this.UpdateInputBuffer(this.AccessibleRange.Start);
                    this.MoveToOffset(this.mCOffset);
                    //this.ValidateCaret();
                }
            }

            private void OnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
            {
                if (e.KeyCode > Keys.Space && e.KeyCode < Keys.Select)
                {
                    this.CaretManger.Freeze();
                    this.InSelection = e.Shift;
                    this.CaretManger.Visible = !e.Shift;
                    double moveto = (!e.Control && (this.mCOffset % 1) != 0) ? 
                        Math.Floor(this.mCOffset) : this.mCOffset;

                    if (e.KeyCode < Keys.End)
                    {
                        int mulby = ((e.KeyCode == Keys.PageUp) ? -1 : 1);
                        this.MoveByLine(this.BelongsTo.Rows * mulby);
                        moveto += this.BelongsTo.TableSize * mulby;
                    }
                    else if (e.KeyCode > Keys.Home)
                    {
                        int id = (int)e.KeyCode - 39;
                        moveto += (Math.Abs(id) != 1) ? 
                            (id + ((e.Control)? this.InFocus.CellProperties.Dividend : 1)) : id * this.BelongsTo.Columns;
                    }
                    else { moveto = (e.KeyCode == Keys.Home)? 0 : this.BelongsTo.Source.Length; }
                    this.MoveToOffset(moveto);
                }
            }

            protected virtual void OnKeyUp(object sender, KeyEventArgs e)
            {
                if (e.KeyCode > Keys.Space && e.KeyCode < Keys.Select)
                {
                    this.CaretManger.Melt();
                }
            }

            protected virtual void OnKeyPress(object sender, KeyPressEventArgs e)
            {
                if (this.InFocus.Name.Equals("Hex"))
                {
                    //Regex.IsMatch(e.KeyChar.ToString(), "[0-9A-E]+", RegexOptions.IgnoreCase)
                    //TODO: calc exact offset
                    this.BelongsTo.Source.Write((long)this.mCOffset, 
                        (short)((this.mCOffset % 1 == 0)? 1 : 2), (byte)e.KeyChar);
                    this.mOffsetsRange.Add(new Range((long)this.mCOffset, (long)this.mCOffset), 2);
                    this.MoveToOffset(this.mCOffset + this.InFocus.CellProperties.Dividend);
                }
                else {
                    this.BelongsTo.Source.Write((long)this.mCOffset, 0, (byte)e.KeyChar);
                    this.mOffsetsRange.Add(new Range((long)this.mCOffset, (long)this.mCOffset), 2);
                    this.MoveToOffset(this.mCOffset + this.InFocus.CellProperties.Dividend);
                }
            }

            private void OnSegmentFocusChanged(object sender, SegmentEventArgs e)
            {
                if (e.Segment is HexView.PageSegment)
                {
                    this.CaretManger[this.InFocus.NavIndex].Vertical = true;
                    this.InFocus = (HexView.PageSegment)e.Segment;
                    this.CaretManger[this.InFocus.NavIndex].Vertical = false;
                }
            }

            protected virtual void FocusChanged(object sender, EventArgs e)
            {
                this.ValidateCaret();
            }

            protected virtual void OnMouseWheel(object sender, MouseEventArgs e)
            {
                this.MoveByLine(
                    (e.Delta / SystemInformation.MouseWheelScrollDelta) * -SystemInformation.MouseWheelScrollLines);
                this.UpdateBuffer();
            }

            protected virtual void OnMouseDown(object sender, MouseEventArgs e)
            {
                Point pt = ControlPage.PageToSegment(this.InFocus, e.Location);
                if(pt.X > -1 && pt.Y > -1)
                    this.SetCaret(this.InFocus.CoordinatesOf(pt.X, pt.Y));
            }

            protected virtual void OnScroll(object sender, ScrollEventArgs e)
            {
                if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
                {
                    this.MoveToLine(e.NewValue);
                    this.UpdateBuffer();
                }
            }

            public void AddOffsetState(Range range, int offsetState)
            {
                this.mOffsetsRange.Add(range, offsetState);
            }

            public void RemoveOffsetState(Range range, int offsetState)
            {
                this.mOffsetsRange.Remove(range, offsetState);
            }
        }

        #endregion
    }
}
